home *** CD-ROM | disk | FTP | other *** search
Wrap
/* * This module contains all the operations that are provided by CsFire. This * controller is used by the XPCOM service and actually handles all operations. */ var EXPORTED_SYMBOLS = [ ]; Components.utils.import("resource://csfiremodules/CsFireCommon.jsm"); CsFire.CsFireController = new function() { this.authenticatedRequests = []; } /* * Accepts information about user interaction (destination and source URI)and * registers this with the data service. */ CsFire.CsFireController.registerUserInteraction = function(dst, src) { CsFire.DataService.registerUserInteraction(dst, src); } /* * Accepts information from the browser about a request that is about to be * made (destination, source, content type and context (DOM)) and stores it with * the data service. */ CsFire.CsFireController.registerLoadRequest = function(dstUri, srcUri, contentType, context) { // Check if request is internal or not if(!CsFire.HttpUtils.isRequestInternal(dstUri)) { // Only external requests are stored CsFire.DataService.registerLoadRequest(dstUri, srcUri, contentType, context); } } /* * Accepts information about an HTTP request (the HTTP channel object) and stores * it with the data service. */ CsFire.CsFireController.registerHttpRequest = function(httpChannel) { // Check if request is internal or not if(!CsFire.HttpUtils.isRequestInternal(httpChannel.URI)) { // Only external requests are stored CsFire.DataService.registerHttpRequest(httpChannel); } } /* * Makes a decision about a request (using the destination URI and the HTTP * channel). This will have a changing effect on the HTTP channel, based on the * decision coming from the policies. */ CsFire.CsFireController.decideForURI = function(uri, httpChannel) { // Check if request is internal or not if(!CsFire.HttpUtils.isRequestInternal(uri)) { var data = CsFire.DataService.getAndRemoveRequestData(uri); var decision = CsFire.PolicyManager.decide(data); this._handleDecision(decision, httpChannel); } } /* * Processes an HTTP response (needed for working with authentication headers * and redirects). This will not have an effect on the response, unless * authentication needs to be done explicitly by the user. */ CsFire.CsFireController.registerHttpResponse = function(httpChannel) { var stringUri = httpChannel.URI.prePath + httpChannel.URI.path; /* * Clear the loading flag if set. This will cause firefox to think that * authentication info is required, so the required info will be fetched. * This can either be done from cache or from the user. If the info * comes from the cache, we still have a CSRF issue. This needs to be * handled, as discussed in stripAuthenticationFromRequest. */ if(CsFire.PolicyManager.isEnabled() && httpChannel.loadFlags & Components.interfaces.nsIRequest.LOAD_ANONYMOUS) { CsFire.Logger.debug("Disabling anonymous loading mode: " + stringUri); httpChannel.loadFlags &= ~( Components.interfaces.nsIRequest.LOAD_ANONYMOUS); var authPath = CsFire.HttpUtils.getAuthenticatedPath(httpChannel.URI); var status = httpChannel.responseStatus; if(status == 401 || status == 407) { // Access denied, so check if it's because we added a bogus header try { var authHeader = httpChannel.getRequestHeader("authorization"); if(authHeader.indexOf("csfire") != -1) { /* * Whoops, our fault, so mark the request as unauthenticated, * since Firefox has cleared it's cached credentials by now. */ this.authenticatedRequests[authPath] = 0; CsFire.Logger.debug("Unmarking authenticated request: " + authPath); } } catch(e) {} } else { /* * Not an "access denied" status, so the request has been authorized. * If we have indeed an authorization header, mark the path of the * request as authenticated. */ try { httpChannel.getRequestHeader("authorization"); // Header found! this.authenticatedRequests[authPath] = 1; CsFire.Logger.debug("Marking authenticated request: " + authPath); } catch(e) {} } } /* * When 2 redirects using the Location header are chained, the originalURI * stays the same for both redirects. In the following redirect case: * x.com --> y.com/foo/ --> y.com/bar/ * the httpChannel would contain the pair x.com --> y.com for the last * redirect, which causes wrong policy enforcement actions. * * The solution to this problem is to make sure whenever a Location- * redirect is used, the originalURI is set to the last redirector. */ try { httpChannel.getResponseHeader("Location"); try { httpChannel.originalURI = httpChannel.URI; CsFire.Logger.debug("Modified originalURI on redirect to: " + httpChannel.URI.prePath + "\n"); } catch(e2) { CsFire.Logger.error("Error modifying originalURI on redirect: " + e2); } } catch(e1) {} } /* * Makes sure the decision of the policy is enforced on the HTTP channel. */ CsFire.CsFireController._handleDecision = function(decision, channel) { switch(decision.action) { case CsFire.Policy.DISABLED: break; case CsFire.Policy.ACCEPT: break; case CsFire.Policy.STRIP: this._stripAuthentication(decision, channel); break; case CsFire.Policy.BLOCK: channel.cancel(Components.results.NS_BINDING_ABORTED); break; default: CsFire.Logger.error("Unknown decision type: " + decision.action); break; } } /* * Strips authentication credentials (Authentication headers and cookies) from * the HTTP channel, if this is specified by the decision. */ CsFire.CsFireController._stripAuthentication = function(decision, channel) { var stringUri = channel.URI.prePath + channel.URI.path; if(decision.stripCookies != null && decision.stripCookies == true) { this._removeCookies(channel, decision.cookieExceptions); } if(decision.stripAuth != null && decision.stripAuth == true) { /* TODO Enable stripping of authentication headers * Code is disabled due to bug 537670 * see https://bugzilla.mozilla.org/show_bug.cgi?id=537670 */ //this._removeAuthHeaders(channel); } } /* * Removes all cookies from the outgoing request, except for those specified * in the exceptions list. */ CsFire.CsFireController._removeCookies = function(httpChannel, exceptions) { if(exceptions == null) { // Remove all cookies httpChannel.setRequestHeader("cookie", "", false); } else { try { var cookies = httpChannel.getRequestHeader("cookie").split("; "); var newCookies = ""; for(i in cookies) { try { var cookie = cookies[i]; var cookieName = cookie.split("=")[0]; if(exceptions[cookieName] != null) { if(newCookies != "") { newCookies += ";"; } newCookies += cookie; } } catch(e) { /* prevent one cookie error from killing all cookie operations */ } } httpChannel.setRequestHeader("cookie", newCookies, false); } catch(e) { /* no cookies present, saves us the trouble */ } } } /* * Removes the authentication headers from the request. This uses a complicated * mechanism, since they are probably not yet available (see comments in the * code). */ CsFire.CsFireController._removeAuthHeaders = function(httpChannel) { var stringUri = httpChannel.URI.prePath + httpChannel.URI.path; CsFire.Logger.debug("Enabling anonymous loading mode: " + stringUri); /* * Since Firefox 3.5, the flag LOAD_ANONYMOUS can be used to prevent * Firefox from adding user-specific data to the request (auth headers, cookies). * If we set it here, it's too late for cookies (wich we remove separately), * but not for auth headers. * * The problem with HTTP auth is that if a 401 returns, Firefox will look * for credentials while processing the *response*. If there are cached * credentials (which there are in a CSRF attack), they will be used and * the request will succeed (not what we want). * * To overcome this issue, we see whether the path (RFC2617 below) already has been * authenticated before (set in processHttpResponse) and if so, change the * authentication header to an invalid one. This will cause Firefox to * discard the cached credentials and re-prompt the user when processing * the response. * * RFC2617 states: A client SHOULD assume that all paths at or deeper than the depth of * the last symbolic element in the path field of the Request-URI also * are within the protection space specified by the Basic realm value of * the current challenge. */ httpChannel.loadFlags |= Components.interfaces.nsIRequest.LOAD_ANONYMOUS; // Check to see if we need to modify any response headers try { var authHeader = httpChannel.getRequestHeader("authorization"); // No failures here, so there already is an auth header present var authPath = CsFire.HttpUtils.getAuthenticatedPath(httpChannel.URI); if(this.authenticatedRequests[authPath] == 1) { // We're dealing with an authenticated path, so modify the header httpChannel.setRequestHeader("authorization", "Basic csfire=", false); CsFire.Logger.debug("Cached authorization header, modifying: " + authPath); } else { CsFire.Logger.debug("Fresh authorization header found, not touching"); } } catch(e) { CsFire.Logger.debug("No authorization header found, not removing explicitly"); } } /* * This code will be executed on after a new installation or after an upgrade */ CsFire.CsFireController.performInstallAction = function() { var version = CsFire.PreferenceUtils.getStringPreference("extensions.csfire.version", -1) var current = Components.classes["@mozilla.org/extensions/manager;1"].getService(Components.interfaces.nsIExtensionManager).getItemForID("csfire@cs.kuleuven.be").version; if(version < current) { CsFire.PreferenceUtils.setStringPreference("extensions.csfire.version", current); if(version == -1) { //This is a new installation CsFire.Logger.info("New Installation detected, loading configuration ..."); CsFire.Whitelist.addEntry("google.*", "google.*"); CsFire.Whitelist.addEntry("livefilestore.com", "skydrive.live.com"); CsFire.Whitelist.addEntry("skydrive.live.com", "livefilestore.com"); } else { //This is an update CsFire.Logger.info("Update detected"); /* * To add update code, make sure the old version number is smaller * than the version which needs the update code, then execute it. */ } } }